/***************************************************************************
 * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite        *
 * Copyright (C) 2014 Konloch - Konloch.com / BytecodeViewer.com           *
 *                                                                         *
 * This program is free software: you can redistribute it and/or modify    *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

package the.bytecode.club.bytecodeviewer.gui.hexviewer;

import org.exbin.auxiliary.binary_data.BinaryData;
import org.exbin.bined.CodeAreaCaretPosition;
import org.exbin.bined.DataChangedListener;
import org.exbin.bined.swing.basic.CodeArea;

import javax.annotation.ParametersAreNonnullByDefault;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.math.BigInteger;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.InputMismatchException;
import java.util.Objects;
import org.exbin.bined.CodeAreaCaretListener;

/**
 * Values side panel.
 *
 * @author hajdam
 */
@ParametersAreNonnullByDefault
public class ValuesPanel extends javax.swing.JPanel
{

    public static final int UBYTE_MAX_VALUE = 255;
    public static final int SWORD_MIN_VALUE = -32768;
    public static final int SWORD_MAX_VALUE = 32767;
    public static final int UWORD_MAX_VALUE = 65535;
    public static final long UINT_MAX_VALUE = 4294967295L;
    public static final BigInteger ULONG_MAX_VALUE = new BigInteger("4294967295");
    public static final BigInteger BIG_INTEGER_BYTE_MASK = BigInteger.valueOf(255);
    public static final String VALUE_OUT_OF_RANGE = "Value is out of range";
    public static int CACHE_SIZE = 250;

    private CodeArea codeArea;
    private long dataPosition;
    private DataChangedListener dataChangedListener;
    private CodeAreaCaretListener caretMovedListener;

    private final byte[] valuesCache = new byte[CACHE_SIZE];
    private final ByteBuffer byteBuffer = ByteBuffer.wrap(valuesCache);
    private final ValuesUpdater valuesUpdater = new ValuesUpdater();

    private javax.swing.JRadioButton bigEndianRadioButton;
    private javax.swing.JCheckBox binaryCheckBox0;
    private javax.swing.JCheckBox binaryCheckBox1;
    private javax.swing.JCheckBox binaryCheckBox2;
    private javax.swing.JCheckBox binaryCheckBox3;
    private javax.swing.JCheckBox binaryCheckBox4;
    private javax.swing.JCheckBox binaryCheckBox5;
    private javax.swing.JCheckBox binaryCheckBox6;
    private javax.swing.JCheckBox binaryCheckBox7;
    private javax.swing.JLabel binaryLabel;
    private javax.swing.JLabel byteLabel;
    private javax.swing.JTextField byteTextField;
    private javax.swing.JLabel characterLabel;
    private javax.swing.JTextField characterTextField;
    private javax.swing.JLabel doubleLabel;
    private javax.swing.JTextField doubleTextField;
    private javax.swing.ButtonGroup endianButtonGroup;
    private javax.swing.JLabel floatLabel;
    private javax.swing.JTextField floatTextField;
    private javax.swing.JLabel intLabel;
    private javax.swing.JTextField intTextField;
    private javax.swing.ButtonGroup integerSignButtonGroup;
    private javax.swing.JSeparator jSeparator1;
    private javax.swing.JRadioButton littleEndianRadioButton;
    private javax.swing.JLabel longLabel;
    private javax.swing.JTextField longTextField;
    private javax.swing.JRadioButton signedRadioButton;
    private javax.swing.JLabel stringLabel;
    private javax.swing.JTextField stringTextField;
    private javax.swing.JRadioButton unsignedRadioButton;
    private javax.swing.JLabel wordLabel;
    private javax.swing.JTextField wordTextField;

    public ValuesPanel()
    {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents()
    {

        endianButtonGroup = new javax.swing.ButtonGroup();
        integerSignButtonGroup = new javax.swing.ButtonGroup();
        binaryLabel = new javax.swing.JLabel();
        binaryCheckBox0 = new javax.swing.JCheckBox();
        binaryCheckBox1 = new javax.swing.JCheckBox();
        binaryCheckBox2 = new javax.swing.JCheckBox();
        binaryCheckBox3 = new javax.swing.JCheckBox();
        binaryCheckBox4 = new javax.swing.JCheckBox();
        binaryCheckBox5 = new javax.swing.JCheckBox();
        binaryCheckBox6 = new javax.swing.JCheckBox();
        binaryCheckBox7 = new javax.swing.JCheckBox();
        byteLabel = new javax.swing.JLabel();
        byteTextField = new javax.swing.JTextField();
        wordLabel = new javax.swing.JLabel();
        wordTextField = new javax.swing.JTextField();
        intLabel = new javax.swing.JLabel();
        intTextField = new javax.swing.JTextField();
        longLabel = new javax.swing.JLabel();
        longTextField = new javax.swing.JTextField();
        floatLabel = new javax.swing.JLabel();
        floatTextField = new javax.swing.JTextField();
        doubleLabel = new javax.swing.JLabel();
        doubleTextField = new javax.swing.JTextField();
        characterLabel = new javax.swing.JLabel();
        characterTextField = new javax.swing.JTextField();
        stringLabel = new javax.swing.JLabel();
        stringTextField = new javax.swing.JTextField();
        jSeparator1 = new javax.swing.JSeparator();
        bigEndianRadioButton = new javax.swing.JRadioButton();
        littleEndianRadioButton = new javax.swing.JRadioButton();
        signedRadioButton = new javax.swing.JRadioButton();
        unsignedRadioButton = new javax.swing.JRadioButton();

        setMaximumSize(new java.awt.Dimension(246, 447));
        setMinimumSize(new java.awt.Dimension(246, 447));

        binaryLabel.setText("Binary");

        binaryCheckBox0.addActionListener(this::binaryCheckBox0ActionPerformed);
        binaryCheckBox1.addActionListener(this::binaryCheckBox1ActionPerformed);
        binaryCheckBox2.addActionListener(this::binaryCheckBox2ActionPerformed);
        binaryCheckBox3.addActionListener(this::binaryCheckBox3ActionPerformed);
        binaryCheckBox4.addActionListener(this::binaryCheckBox4ActionPerformed);
        binaryCheckBox5.addActionListener(this::binaryCheckBox5ActionPerformed);
        binaryCheckBox6.addActionListener(this::binaryCheckBox6ActionPerformed);
        binaryCheckBox7.addActionListener(this::binaryCheckBox7ActionPerformed);

        byteLabel.setText("Byte");

        byteTextField.setEditable(false);
        byteTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                byteTextFieldKeyReleased(evt);
            }
        });

        wordLabel.setText("Word");

        wordTextField.setEditable(false);
        wordTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                wordTextFieldKeyReleased(evt);
            }
        });

        intLabel.setText("Integer");

        intTextField.setEditable(false);
        intTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                intTextFieldKeyReleased(evt);
            }
        });

        longLabel.setText("Long");

        longTextField.setEditable(false);
        longTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                longTextFieldKeyReleased(evt);
            }
        });

        floatLabel.setText("Float");

        floatTextField.setEditable(false);
        floatTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                floatTextFieldKeyReleased(evt);
            }
        });

        doubleLabel.setText("Double");

        doubleTextField.setEditable(false);
        doubleTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                doubleTextFieldKeyReleased(evt);
            }
        });

        characterLabel.setText("Character");

        characterTextField.setEditable(false);
        characterTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                characterTextFieldKeyReleased(evt);
            }
        });

        stringLabel.setText("String");

        stringTextField.setEditable(false);
        stringTextField.addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyReleased(java.awt.event.KeyEvent evt)
            {
                stringTextFieldKeyReleased(evt);
            }
        });

        jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL);

        endianButtonGroup.add(bigEndianRadioButton);
        bigEndianRadioButton.setSelected(true);
        bigEndianRadioButton.setText("BE");
        bigEndianRadioButton.setToolTipText("Big Endian");
        bigEndianRadioButton.addChangeListener(this::bigEndianRadioButtonStateChanged);

        endianButtonGroup.add(littleEndianRadioButton);
        littleEndianRadioButton.setText("LE");
        littleEndianRadioButton.setToolTipText("Little Endian");
        littleEndianRadioButton.addChangeListener(this::littleEndianRadioButtonStateChanged);

        integerSignButtonGroup.add(signedRadioButton);
        signedRadioButton.setSelected(true);
        signedRadioButton.setText("Sig");
        signedRadioButton.setToolTipText("Signed Integers");
        signedRadioButton.addChangeListener(this::signedRadioButtonStateChanged);

        integerSignButtonGroup.add(unsignedRadioButton);
        unsignedRadioButton.setText("Uns");
        unsignedRadioButton.setToolTipText("Unsigned Integers");
        unsignedRadioButton.addChangeListener(this::unsignedRadioButtonStateChanged);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addComponent(bigEndianRadioButton).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(littleEndianRadioButton).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(signedRadioButton).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(unsignedRadioButton)).addComponent(binaryLabel).addGroup(layout.createSequentialGroup().addComponent(binaryCheckBox0).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(binaryCheckBox1).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(binaryCheckBox2).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(binaryCheckBox3).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(binaryCheckBox4).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(binaryCheckBox5).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(binaryCheckBox6).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(binaryCheckBox7)).addGroup(layout.createSequentialGroup().addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(byteTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(wordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(intTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(longTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(byteLabel).addComponent(wordLabel).addComponent(intLabel).addComponent(longLabel)).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(characterTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(floatTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(floatLabel).addComponent(doubleLabel).addComponent(doubleTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(characterLabel).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(stringTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(stringLabel))))).addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));
        layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addContainerGap().addComponent(binaryLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING).addComponent(binaryCheckBox0).addComponent(binaryCheckBox1).addComponent(binaryCheckBox2).addComponent(binaryCheckBox3).addComponent(binaryCheckBox4).addComponent(binaryCheckBox5).addComponent(binaryCheckBox6).addComponent(binaryCheckBox7)).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING).addGroup(layout.createSequentialGroup().addComponent(byteLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(byteTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(wordLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(wordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(intLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(intTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(longLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(longTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)).addGroup(layout.createSequentialGroup().addComponent(floatLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(floatTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(doubleLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(doubleTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(characterLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(characterTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(stringLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(stringTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE).addComponent(bigEndianRadioButton).addComponent(littleEndianRadioButton)).addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE).addComponent(signedRadioButton).addComponent(unsignedRadioButton))).addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));
    }// </editor-fold>//GEN-END:initComponents

    private void littleEndianRadioButtonStateChanged(javax.swing.event.ChangeEvent evt)
    {//GEN-FIRST:event_littleEndianRadioButtonStateChanged
        updateValues();
    }//GEN-LAST:event_littleEndianRadioButtonStateChanged

    private void bigEndianRadioButtonStateChanged(javax.swing.event.ChangeEvent evt)
    {//GEN-FIRST:event_bigEndianRadioButtonStateChanged
        updateValues();
    }//GEN-LAST:event_bigEndianRadioButtonStateChanged

    private void signedRadioButtonStateChanged(javax.swing.event.ChangeEvent evt)
    {//GEN-FIRST:event_signedRadioButtonStateChanged
        updateValues();
    }//GEN-LAST:event_signedRadioButtonStateChanged

    private void unsignedRadioButtonStateChanged(javax.swing.event.ChangeEvent evt)
    {//GEN-FIRST:event_unsignedRadioButtonStateChanged
        updateValues();
    }//GEN-LAST:event_unsignedRadioButtonStateChanged

    private void binaryCheckBox0ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox0ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x80) > 0 != binaryCheckBox0.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x80);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox0ActionPerformed

    private void binaryCheckBox1ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox1ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x40) > 0 != binaryCheckBox1.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x40);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox1ActionPerformed

    private void binaryCheckBox2ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox2ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x20) > 0 != binaryCheckBox2.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x20);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox2ActionPerformed

    private void binaryCheckBox3ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox3ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x10) > 0 != binaryCheckBox3.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x10);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox3ActionPerformed

    private void binaryCheckBox4ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox4ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x8) > 0 != binaryCheckBox4.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x8);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox4ActionPerformed

    private void binaryCheckBox5ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox5ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x4) > 0 != binaryCheckBox5.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x4);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox5ActionPerformed

    private void binaryCheckBox6ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox6ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x2) > 0 != binaryCheckBox6.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x2);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox6ActionPerformed

    private void binaryCheckBox7ActionPerformed(java.awt.event.ActionEvent evt)
    {//GEN-FIRST:event_binaryCheckBox7ActionPerformed
        if (!valuesUpdater.isUpdateInProgress()
            && ((valuesCache[0] & 0x1) > 0 != binaryCheckBox7.isSelected()))
        {
            valuesCache[0] = (byte) (valuesCache[0] ^ 0x1);
            modifyValues(1);
        }
    }//GEN-LAST:event_binaryCheckBox7ActionPerformed

    private void byteTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_byteTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                int intValue = Integer.parseInt(byteTextField.getText());
                if (isSigned())
                {
                    if (intValue < Byte.MIN_VALUE || intValue > Byte.MAX_VALUE)
                        throw new NumberFormatException(VALUE_OUT_OF_RANGE);
                }
                else
                {
                    if (intValue < 0 || intValue > UBYTE_MAX_VALUE)
                        throw new NumberFormatException(VALUE_OUT_OF_RANGE);
                }

                valuesCache[0] = (byte) intValue;
                modifyValues(1);
                updateValues();
            }
            catch (NumberFormatException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_byteTextFieldKeyReleased

    private void wordTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_wordTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                int intValue = Integer.parseInt(wordTextField.getText());
                if (isSigned())
                {
                    if (intValue < SWORD_MIN_VALUE || intValue > SWORD_MAX_VALUE)
                        throw new NumberFormatException(VALUE_OUT_OF_RANGE);
                }
                else
                {
                    if (intValue < 0 || intValue > UWORD_MAX_VALUE)
                        throw new NumberFormatException(VALUE_OUT_OF_RANGE);
                }

                if (getByteOrder() == ByteOrder.LITTLE_ENDIAN)
                {
                    valuesCache[0] = (byte) (intValue & 0xff);
                    valuesCache[1] = (byte) ((intValue >> 8) & 0xff);
                }
                else
                {
                    valuesCache[0] = (byte) ((intValue >> 8) & 0xff);
                    valuesCache[1] = (byte) (intValue & 0xff);
                }

                modifyValues(2);
                updateValues();
            }
            catch (NumberFormatException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_wordTextFieldKeyReleased

    private void intTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_intTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                long longValue = Long.parseLong(intTextField.getText());
                if (isSigned())
                {
                    if (longValue < Integer.MIN_VALUE || longValue > Integer.MAX_VALUE)
                        throw new NumberFormatException(VALUE_OUT_OF_RANGE);
                }
                else
                {
                    if (longValue < 0 || longValue > UINT_MAX_VALUE)
                        throw new NumberFormatException(VALUE_OUT_OF_RANGE);
                }

                if (getByteOrder() == ByteOrder.LITTLE_ENDIAN)
                {
                    valuesCache[0] = (byte) (longValue & 0xff);
                    valuesCache[1] = (byte) ((longValue >> 8) & 0xff);
                    valuesCache[2] = (byte) ((longValue >> 16) & 0xff);
                    valuesCache[3] = (byte) ((longValue >> 24) & 0xff);
                }
                else
                {
                    valuesCache[0] = (byte) ((longValue >> 24) & 0xff);
                    valuesCache[1] = (byte) ((longValue >> 16) & 0xff);
                    valuesCache[2] = (byte) ((longValue >> 8) & 0xff);
                    valuesCache[3] = (byte) (longValue & 0xff);
                }

                modifyValues(4);
                updateValues();
            }
            catch (NumberFormatException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_intTextFieldKeyReleased

    private void longTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_longTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                ByteOrder byteOrder = getByteOrder();
                if (isSigned())
                {
                    long longValue = Long.parseLong(longTextField.getText());

                    ((Buffer) byteBuffer).rewind();
                    if (byteBuffer.order() != byteOrder)
                        byteBuffer.order(byteOrder);

                    byteBuffer.putLong(longValue);
                }
                else
                {
                    BigInteger bigInteger = new BigInteger(longTextField.getText());
                    if (bigInteger.signum() == -1 || bigInteger.compareTo(ULONG_MAX_VALUE) > 0)
                        throw new NumberFormatException(VALUE_OUT_OF_RANGE);

                    if (byteOrder == ByteOrder.LITTLE_ENDIAN)
                    {
                        for (int i = 0; i < 7; i++)
                        {
                            BigInteger nextByte = bigInteger.and(BIG_INTEGER_BYTE_MASK);
                            valuesCache[7 - i] = nextByte.byteValue();
                            bigInteger = bigInteger.shiftRight(8);
                        }
                    }
                    else
                    {
                        for (int i = 0; i < 7; i++)
                        {
                            BigInteger nextByte = bigInteger.and(BIG_INTEGER_BYTE_MASK);
                            valuesCache[i] = nextByte.byteValue();
                            bigInteger = bigInteger.shiftRight(8);
                        }
                    }
                }

                modifyValues(8);
                updateValues();
            }
            catch (NumberFormatException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_longTextFieldKeyReleased

    private void floatTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_floatTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                ByteOrder byteOrder = getByteOrder();
                float floatValue = Float.parseFloat(floatTextField.getText());

                ((Buffer) byteBuffer).rewind();

                if (byteBuffer.order() != byteOrder)
                    byteBuffer.order(byteOrder);

                byteBuffer.putFloat(floatValue);

                modifyValues(4);
                updateValues();
            }
            catch (NumberFormatException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_floatTextFieldKeyReleased

    private void doubleTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_doubleTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                ByteOrder byteOrder = getByteOrder();
                double doubleValue = Double.parseDouble(doubleTextField.getText());

                ((Buffer) byteBuffer).rewind();

                if (byteBuffer.order() != byteOrder)
                    byteBuffer.order(byteOrder);

                byteBuffer.putDouble(doubleValue);

                modifyValues(8);
                updateValues();
            }
            catch (NumberFormatException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_doubleTextFieldKeyReleased

    private void characterTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_characterTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                String characterText = characterTextField.getText();

                if (characterText.length() == 0)
                    throw new InputMismatchException("Empty value not valid");

                if (characterText.length() > 1)
                    throw new InputMismatchException("Only single character allowed");

                byte[] bytes = characterText.getBytes(codeArea.getCharset());
                System.arraycopy(bytes, 0, valuesCache, 0, bytes.length);

                modifyValues(bytes.length);
                updateValues();
            }
            catch (InputMismatchException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_characterTextFieldKeyReleased

    private void stringTextFieldKeyReleased(java.awt.event.KeyEvent evt)
    {//GEN-FIRST:event_stringTextFieldKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable())
        {
            try
            {
                String characterText = stringTextField.getText();

                if (characterText.length() == 0)
                    throw new InputMismatchException("Empty value not valid");

                byte[] bytes = characterText.getBytes(codeArea.getCharset());

                if (bytes.length > CACHE_SIZE)
                    throw new InputMismatchException("String is too long");

                System.arraycopy(bytes, 0, valuesCache, 0, bytes.length);

                modifyValues(bytes.length);
                updateValues();
            }
            catch (InputMismatchException ex)
            {
                showException(ex);
            }
        }
    }//GEN-LAST:event_stringTextFieldKeyReleased

    public void setCodeArea(CodeArea codeArea)
    {
        this.codeArea = codeArea;
    }

    public void enableUpdate()
    {
        dataChangedListener = () ->
        {
            updateEditMode();
            updateValues();
        };

        codeArea.addDataChangedListener(dataChangedListener);
        caretMovedListener = (CodeAreaCaretPosition caretPosition) -> updateValues();
        codeArea.addCaretMovedListener(caretMovedListener);

        updateEditMode();
        updateValues();
    }

    public void disableUpdate()
    {
        codeArea.removeDataChangedListener(dataChangedListener);
        codeArea.removeCaretMovedListener(caretMovedListener);
    }

    public void updateEditMode()
    {
        boolean editable = isEditable();

        binaryCheckBox0.setEnabled(editable);
        binaryCheckBox1.setEnabled(editable);
        binaryCheckBox2.setEnabled(editable);
        binaryCheckBox3.setEnabled(editable);
        binaryCheckBox4.setEnabled(editable);
        binaryCheckBox5.setEnabled(editable);
        binaryCheckBox6.setEnabled(editable);
        binaryCheckBox7.setEnabled(editable);
        byteTextField.setEditable(editable);
        wordTextField.setEditable(editable);
        intTextField.setEditable(editable);
        longTextField.setEditable(editable);
        floatTextField.setEditable(editable);
        doubleTextField.setEditable(editable);
        characterTextField.setEditable(editable);
        stringTextField.setEditable(editable);
    }

    public void updateValues()
    {
        CodeAreaCaretPosition caretPosition = codeArea.getActiveCaretPosition();
        dataPosition = caretPosition.getDataPosition();
        long dataSize = codeArea.getDataSize();

        if (dataPosition < dataSize)
        {
            int availableData = dataSize - dataPosition >= CACHE_SIZE ? CACHE_SIZE : (int) (dataSize - dataPosition);
            BinaryData contentData = Objects.requireNonNull(codeArea.getContentData());
            contentData.copyToArray(dataPosition, valuesCache, 0, availableData);

            if (availableData < CACHE_SIZE)
                Arrays.fill(valuesCache, availableData, CACHE_SIZE, (byte) 0);
        }

        valuesUpdater.schedule();
    }

    private void modifyValues(int bytesCount)
    {
        // Unsupported in this version
    }

    private boolean isSigned()
    {
        return signedRadioButton.isSelected();
    }

    private boolean isEditable()
    {
        return codeArea.isEditable();
    }

    private ByteOrder getByteOrder()
    {
        return littleEndianRadioButton.isSelected() ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
    }

    private void showException(Exception ex)
    {
        JOptionPane.showMessageDialog(this, ex.getMessage(), "Invalid Input", JOptionPane.ERROR_MESSAGE);
    }

    public enum ValuesPanelField
    {
        BINARY0,
        BINARY1,
        BINARY2,
        BINARY3,
        BINARY4,
        BINARY5,
        BINARY6,
        BINARY7,
        BYTE,
        WORD,
        INTEGER,
        LONG,
        FLOAT,
        DOUBLE,
        CHARACTER,
        STRING
    }

    @ParametersAreNonnullByDefault
    private class ValuesUpdater
    {

        private boolean updateInProgress = false;
        private boolean updateTerminated = false;
        private boolean scheduleUpdate = false;
        private boolean clearFields = true;

        private boolean signed;
        private ByteOrder byteOrder;
        private byte[] values;

        private synchronized void schedule()
        {
            if (updateInProgress)
                updateTerminated = true;

            if (!scheduleUpdate)
            {
                scheduleUpdate = true;
                scheduleNextStep(ValuesPanelField.values()[0]);
            }
        }

        private void scheduleNextStep(ValuesPanelField valuesPanelField)
        {
            SwingUtilities.invokeLater(() -> updateValue(valuesPanelField));
        }

        public boolean isUpdateInProgress()
        {
            return updateInProgress;
        }

        private void updateValue(ValuesPanelField valuesPanelField)
        {
            if (valuesPanelField.ordinal() == 0)
            {
                long dataSize = codeArea.getDataSize();

                clearFields = dataPosition >= dataSize;
                byteOrder = getByteOrder();
                signed = isSigned();
                values = valuesCache;

                if (clearFields)
                    values[0] = 0;

                updateStarted();
            }

            if (updateTerminated)
            {
                stopUpdate();
                return;
            }

            if (clearFields)
                clearField(valuesPanelField);
            else
                updateField(valuesPanelField);

            final ValuesPanelField[] panelFields = ValuesPanelField.values();
            ValuesPanelField lastValue = panelFields[panelFields.length - 1];

            if (valuesPanelField == lastValue)
                stopUpdate();
            else
            {
                SwingUtilities.invokeLater(() ->
                {
                    ValuesPanelField nextValue = panelFields[valuesPanelField.ordinal() + 1];
                    updateValue(nextValue);
                });
            }
        }

        private void updateField(ValuesPanelField valuesPanelField)
        {
            switch (valuesPanelField)
            {
                case BINARY0:
                {
                    binaryCheckBox0.setSelected((values[0] & 0x80) > 0);
                    break;
                }

                case BINARY1:
                {
                    binaryCheckBox1.setSelected((values[0] & 0x40) > 0);
                    break;
                }

                case BINARY2:
                {
                    binaryCheckBox2.setSelected((values[0] & 0x20) > 0);
                    break;
                }

                case BINARY3:
                {
                    binaryCheckBox3.setSelected((values[0] & 0x10) > 0);
                    break;
                }

                case BINARY4:
                {
                    binaryCheckBox4.setSelected((values[0] & 0x8) > 0);
                    break;
                }

                case BINARY5:
                {
                    binaryCheckBox5.setSelected((values[0] & 0x4) > 0);
                    break;
                }

                case BINARY6:
                {
                    binaryCheckBox6.setSelected((values[0] & 0x2) > 0);
                    break;
                }

                case BINARY7:
                {
                    binaryCheckBox7.setSelected((values[0] & 0x1) > 0);
                    break;
                }

                case BYTE:
                {
                    byteTextField.setText(String.valueOf(signed ? values[0] : values[0] & 0xff));
                    break;
                }

                case WORD:
                {
                    int wordValue = signed ? (byteOrder == ByteOrder.LITTLE_ENDIAN ? (values[0] & 0xff) | (values[1] << 8) : (values[1] & 0xff) | (values[0] << 8)) : (byteOrder == ByteOrder.LITTLE_ENDIAN ? (values[0] & 0xff) | ((values[1] & 0xff) << 8) : (values[1] & 0xff) | ((values[0] & 0xff) << 8));
                    wordTextField.setText(String.valueOf(wordValue));
                    break;
                }

                case INTEGER:
                {
                    long intValue = signed ? (byteOrder == ByteOrder.LITTLE_ENDIAN ? (values[0] & 0xffL) | ((values[1] & 0xffL) << 8) | ((values[2] & 0xffL) << 16) | (values[3] << 24) : (values[3] & 0xffL) | ((values[2] & 0xffL) << 8) | ((values[1] & 0xffL) << 16) | (values[0] << 24)) : (byteOrder == ByteOrder.LITTLE_ENDIAN ? (values[0] & 0xffL) | ((values[1] & 0xffL) << 8) | ((values[2] & 0xffL) << 16) | ((values[3] & 0xffL) << 24) : (values[3] & 0xffL) | ((values[2] & 0xffL) << 8) | ((values[1] & 0xffL) << 16) | ((values[0] & 0xffL) << 24));
                    intTextField.setText(String.valueOf(intValue));
                    break;
                }

                case LONG:
                {
                    if (signed)
                    {
                        ((Buffer) byteBuffer).rewind();

                        if (byteBuffer.order() != byteOrder)
                            byteBuffer.order(byteOrder);

                        longTextField.setText(String.valueOf(byteBuffer.getLong()));
                    }
                    else
                    {
                        long longValue = byteOrder == ByteOrder.LITTLE_ENDIAN ? (values[0] & 0xffL) | ((values[1] & 0xffL) << 8) | ((values[2] & 0xffL) << 16) | ((values[3] & 0xffL) << 24) | ((values[4] & 0xffL) << 32) | ((values[5] & 0xffL) << 40) | ((values[6] & 0xffL) << 48) : (values[7] & 0xffL) | ((values[6] & 0xffL) << 8) | ((values[5] & 0xffL) << 16) | ((values[4] & 0xffL) << 24) | ((values[3] & 0xffL) << 32) | ((values[2] & 0xffL) << 40) | ((values[1] & 0xffL) << 48);
                        BigInteger bigInt1 = BigInteger.valueOf(values[byteOrder == ByteOrder.LITTLE_ENDIAN ? 7 : 0] & 0xffL);
                        BigInteger bigInt2 = bigInt1.shiftLeft(56);
                        BigInteger bigInt3 = bigInt2.add(BigInteger.valueOf(longValue));
                        longTextField.setText(bigInt3.toString());
                    }
                    break;
                }

                case FLOAT:
                {
                    ((Buffer) byteBuffer).rewind();

                    if (byteBuffer.order() != byteOrder)
                        byteBuffer.order(byteOrder);

                    floatTextField.setText(String.valueOf(byteBuffer.getFloat()));
                    break;
                }

                case DOUBLE:
                {
                    ((Buffer) byteBuffer).rewind();

                    if (byteBuffer.order() != byteOrder)
                        byteBuffer.order(byteOrder);

                    doubleTextField.setText(String.valueOf(byteBuffer.getDouble()));
                    break;
                }

                case CHARACTER:
                {
                    String strValue = new String(values, codeArea.getCharset());

                    if (strValue.length() > 0)
                        characterTextField.setText(strValue.substring(0, 1));
                    else
                        characterTextField.setText("");
                    break;
                }

                case STRING:
                {
                    String strValue = new String(values, codeArea.getCharset());
                    for (int i = 0; i < strValue.length(); i++)
                    {
                        char charAt = strValue.charAt(i);

                        if (charAt == '\r' || charAt == '\n' || charAt == 0)
                        {
                            strValue = strValue.substring(0, i);
                            break;
                        }
                    }

                    stringTextField.setText(strValue);
                    stringTextField.setCaretPosition(0);
                    break;
                }
            }
        }

        private void clearField(ValuesPanelField valuesPanelField)
        {
            switch (valuesPanelField)
            {
                case BINARY0:
                {
                    binaryCheckBox0.setSelected(false);
                    break;
                }

                case BINARY1:
                {
                    binaryCheckBox1.setSelected(false);
                    break;
                }

                case BINARY2:
                {
                    binaryCheckBox2.setSelected(false);
                    break;
                }

                case BINARY3:
                {
                    binaryCheckBox3.setSelected(false);
                    break;
                }

                case BINARY4:
                {
                    binaryCheckBox4.setSelected(false);
                    break;
                }

                case BINARY5:
                {
                    binaryCheckBox5.setSelected(false);
                    break;
                }

                case BINARY6:
                {
                    binaryCheckBox6.setSelected(false);
                    break;
                }

                case BINARY7:
                {
                    binaryCheckBox7.setSelected(false);
                    break;
                }

                case BYTE:
                {
                    byteTextField.setText("");
                    break;
                }

                case WORD:
                {
                    wordTextField.setText("");
                    break;
                }

                case INTEGER:
                {
                    intTextField.setText("");
                    break;
                }

                case LONG:
                {
                    longTextField.setText("");
                    break;
                }

                case FLOAT:
                {
                    floatTextField.setText("");
                    break;
                }

                case DOUBLE:
                {
                    doubleTextField.setText("");
                    break;
                }

                case CHARACTER:
                {
                    characterTextField.setText("");
                    break;
                }

                case STRING:
                {
                    stringTextField.setText("");
                    break;
                }
            }
        }

        private synchronized void updateStarted()
        {
            updateInProgress = true;
            scheduleUpdate = false;
        }

        private synchronized void stopUpdate()
        {
            updateInProgress = false;
            updateTerminated = false;
        }
    }
}
